/* ================================================================
*   Copyright (C) 2020 All rights reserved.
*
*   文件名称：xproto.h
*   创 建 者：xunmenglong
*   创建日期：2020年12月03日
*   描    述：这是一个简单的protobuf，将json结构序列化为二进制，并提供反解析能力
*             特色是效率高
*
================================================================ */


#ifndef XPROTO_H
#define XPROTO_H

#include <map>
#include <deque>

#include "xxhash.h"
#include <butil/third_party/rapidjson/document.h>
#include "butil/containers/flat_map.h"

#include "xutil.h"

using namespace butil::rapidjson;
using namespace xutil;

#define XPROTO_MAX_NAME_LEN 128
typedef butil::FlatMap<uint64_t, int> FieldsIdxMap;

// 序列化后的二进制包头
struct package_head_t {
    int total_len = 0;  // 包总长度
    int head_len = 0;   // 其中head的长度
};

struct schema_t;

// 字段信息, 嵌套schema_t, 形成类似json的嵌套结构
struct field_t {
    char name[XPROTO_MAX_NAME_LEN]; // 名字
    int idx;                        // 该字段位于第几个idx
    int type;                       // 类型
    int is_option = 1;              // 是否是必须字段
    int element_type;               // 如果是数组, 数组的对象类型
    schema_t * schema;              // 如果是object/object_array, 那么其子schema的内容
};

// 包含多个字段描述的schema定义
struct schema_t {
    int fields_num;                 // 字段数量
    field_t * fields;               // 字段具体指针
    FieldsIdxMap * idx_map;         // 字段名字和字段指针的映射
};

enum {
    XP_TYPE_INT=1,
    XP_TYPE_UINT,
    XP_TYPE_INT64,
    XP_TYPE_UINT64,
    XP_TYPE_INT8,
    XP_TYPE_UINT8,
    XP_TYPE_INT16,
    XP_TYPE_UINT16,
    XP_TYPE_FLOAT,
    XP_TYPE_DOUBLE,
    XP_TYPE_STRING,
    XP_TYPE_OBJECT,
    XP_TYPE_ARRAY,
};


class xproto {
private:
    schema_t * _schema;
    map<string, int> _TYPE_MAP;

public:
    xproto() {}
    ~xproto() {}

    /**
     * 获得该proto的schema信息
     * 用于在xprotogetter部分获取值的函数中使用
     */
    inline schema_t * get_schema() {
        return _schema;
    }

    /**
     * 初始化xproto对象, 输入为json格式的schema描述字符串
     * 初始化后的对象就具备将符合schema定义的json转换为二进制的能力
     *
     * @param[in] schema_str: json格式的schema描述字符串
     *
     * @return 0 成功;其他失败
     */
    int init(const char * schema_str);

    /**
     * 将符合schema定义的json序列化为二进制
     * 注意，在序列化的时候并未做内存越界检查
     * 所以务必在输入的out_data/head_data/body_data申请足够的空间
     * @TODO 做内存越界检查
     *
     * @param[in] object_json_str: 要转换为的json字符串
     * @param[out] out_data: 序列化后的二进制就存储在这里
     * @param[out] head_data: 临时存储head数据的空间
     * @param[out] body_data: 临时存储body数据的空间
     *
     * @return 二进制包的长度; <0 代表失败
     */
    int serialize(const char * object_json_str, char * out_data,
            char * head_data, char * body_data);

    /**
     * 同上序列化函数, 只是不输入一个字符串, 而是输入一个json对象
     *
     * @param[in] d: 要序列化的json对象
     * @param[out] out_data: 序列化后的二进制就存储在这里
     * @param[out] head_data: 临时存储head数据的空间
     * @param[out] body_data: 临时存储body数据的空间
     */
    int serialize(Document &d, char * out_data,
            char * head_data, char * body_data);

private:
    // 初始化字符串->枚举int的map映射
    void _init_type_map();
    // 分配新的schema空间
    schema_t * _alloc_schema_t(int fields_num);
    // 解析schema的核心函数, 递归处理所有字段
    schema_t * _parse_schema(Value &v);

    /**
     * 序列化核心函数, 递归处理所有对象字段
     *
     * @param[in] v: 待序列化的json_object对象
     * @param[in] schema: 该object对应的schema描述结构
     * @param[out] head_ptr: 序列化后的head结果存储指针
     * @param[out] body_ptr: 序列化后的body结果存储指针
     * @param[in] body_offset: 序列化该object的时候body指针的绝对其实地址
     *                         因为head中要存储body中的绝对地址
     *
     * @return pair<int, int>
     *          -1, -1: 代表失败
     *          <head_len, body_len> 前者代表头的长度; 后者代表body的长度
     */
    pair<int,int> _parse_object(Value &v, schema_t * schema, int * head_ptr,
            char * body_ptr, int body_offset = 0);

    // 序列化核心函数, 递归处理数组字段,
    // 该函数会跟`_parse_object`相互嵌套递归处理
    pair<int, int> _parse_array(Value &v, schema_t * schema, int element_type,
            int * head_ptr, char * body_ptr, int body_offset);
};

class xprotogetter {
private:
    char * body_ptr;

public:
    xprotogetter() {}
    ~xprotogetter() {}

    /**
     * 通过二进制指针初始化getter对象
     * 返回getter句柄, 该句柄会用于后面的v系列,o系列,l系列函数
     */
    inline int * init(char * data) {
        package_head_t * head = (package_head_t *)data;
        char * head_ptr = data + sizeof(package_head_t);
        body_ptr = data + sizeof(package_head_t) + head->head_len;
        return (int *)head_ptr;
    }

    /**
     * v系列函数，即直接获取某个基础字段的值的指针
     * v1: 访问第一层嵌套的值
     * v2: 访问第二层嵌套的值
     * v3: 访问第三层嵌套的值
     * v4: 访问第四层嵌套的值
     * v5: 访问第五层嵌套的值
     * v_n: 访问第n层嵌套的值, 该函数可以跟`get_idxs_by_str`配合
     * v_str: 不需要handler, 直接通过key字符串来获取值
     *
     * 注意: 该函数不管是int/string/double类型,
     *       都返回char *, 在上层再做指针类型转换
     */
    inline char * v1(int * handler, int idx1) {
        if (unlikely(handler == NULL)) {
            return NULL;
        }
        int body_offset = handler[idx1];
        if (unlikely(body_offset < 0)) {
            return NULL;
        }
        return body_ptr + body_offset;
    }

    inline char * v2(int * handler, int idx1, int idx2) {
        if (unlikely(handler == NULL)) {
            return NULL;
        }
        int body_offset1 = handler[idx1];
        if (unlikely(body_offset1 < 0)) {
            return NULL;
        }
        int body_offset2 = handler[body_offset1 + idx2];
        if (body_offset2 < 0) {
            return NULL;
        }
        return body_ptr + body_offset2;
    }

    inline char * v3(int * handler, int idx1, int idx2, int idx3) {
        return v2(o1(handler, idx1), idx2, idx3);
    }

    inline char * v4(int * handler, int idx1, int idx2, int idx3, int idx4) {
        return v2(o1(o1(handler, idx1), idx2), idx3, idx4);
    }

    inline char * v5(int * handler, int idx1, int idx2, int idx3, int idx4, int idx5) {
        return v2(o1(o1(o1(handler, idx1), idx2), idx3), idx4, idx5);
    }

    inline char * v_n(int * handler, int * idx_arr, int arr_len) {
        switch (arr_len) {
            case 1:
                return v1(handler, idx_arr[0]);
            case 2:
                return v2(handler, idx_arr[0], idx_arr[1]);
            default:
                int * cur_handler = handler;
                for (int i=0; i<arr_len-2 && cur_handler; i++) {
                    cur_handler = o1(cur_handler, idx_arr[i]);
                }
                return v2(cur_handler, idx_arr[arr_len-2], idx_arr[arr_len-1]);
        }
        return NULL;
    }

    inline char * v_str(int * handler, schema_t * schema, const char * str) {
        int idx_arr[16];
        int arr_len;
        field_t * field;
        int ret = get_idxs_by_str(schema, str, idx_arr, &arr_len, &field);
        if (ret != 0) {
            LOG(ERROR) << "get idx info by str fail: " << str;
            return NULL;
        }
        return v_n(handler, idx_arr, arr_len);
    }


    /**
     * o系列函数，获取某个object/array对象的句柄
     * o1: 访问第一层嵌套的对象句柄
     * o2: 访问第二层嵌套的对象句柄
     * o3: 访问第三层嵌套的对象句柄
     * o4: 访问第四层嵌套的对象句柄
     * o5: 访问第五层嵌套的对象句柄
     * o_n: 访问第n层嵌套的对象句柄, 该函数可以跟`get_idxs_by_str`配合
     *
     * 注意: 该函数只支持针对object/array类型字段返回句柄
     *       如果输入普通类型的话，会出现不可预期的错误
     */
    inline int * o1(int * handler, int idx1) {
        if (unlikely(handler == NULL)) {
            return NULL;
        }
        int head_offset = handler[idx1];
        if (unlikely(head_offset < 0)) {
            return NULL;
        }
        return handler + head_offset;
    }

    inline int * o2(int * handler, int idx1, int idx2) {
        return o1(o1(handler, idx1), idx2);
    }

    inline int * o3(int * handler, int idx1, int idx2, int idx3) {
        return o1(o1(o1(handler, idx1), idx2), idx3);
    }

    inline int * o4(int * handler, int idx1, int idx2, int idx3, int idx4) {
        return o1(o1(o1(o1(handler, idx1), idx2), idx3), idx4);
    }

    inline int * o5(int * handler, int idx1, int idx2, int idx3, int idx4, int idx5) {
        return o1(o1(o1(o1(o1(handler, idx1), idx2), idx3), idx4), idx5);
    }

    inline int * o_n(int * handler, int * idx_arr, int arr_len) {
        int * cur_handler = handler;
        for (int i=0; i<arr_len && cur_handler; i++) {
            cur_handler = o1(cur_handler, idx_arr[i]);
        }
        return cur_handler;
    }

    /**
     * l系列函数，获取某个array对象的数组长度, 方便遍历
     * l0: 当前数组句柄的长度
     * l1: 访问第二层嵌套的数组句柄长度
     * l2: 访问第三层嵌套的数组句柄长度
     * l3: 访问第四层嵌套的数组句柄长度
     * l4: 访问第五层嵌套的数组句柄长度
     * l5: 访问第五层嵌套的数组句柄长度
     * l_n: 访问第n层嵌套的数组句柄, 该函数可以跟`get_idxs_by_str`配合
     *
     * 注意: 该函数只支持针对array类型字段返回句柄
     *       如果输入普通类型或者object类型字段的话，会出现不可预期的错误
     */
    inline int l0(int * handler) {
        if (unlikely(handler == NULL)) {
            return 0;
        }
        return handler[-1];
    }

    inline int l1(int * handler, int idx1) {
        if (unlikely(handler == NULL)) {
            return 0;
        }
        int head_offset = handler[idx1];
        if (unlikely(head_offset < 0)) {
            return 0;
        }
        return handler[head_offset-1];
    }

    inline int l2(int * handler, int idx1, int idx2) {
        return l1(o1(handler, idx1), idx2);
    }

    inline int l3(int * handler, int idx1, int idx2, int idx3) {
        return l1(o1(o1(handler, idx1), idx2), idx3);
    }

    inline int l4(int * handler, int idx1, int idx2, int idx3, int idx4) {
        return l1(o1(o1(o1(handler, idx1), idx2), idx3), idx4);
    }

    inline int l5(int * handler, int idx1, int idx2, int idx3, int idx4, int idx5) {
        return l1(o1(o1(o1(o1(handler, idx1), idx2), idx3), idx4), idx5);
    }

    inline int l_n(int * handler, int * idx_arr, int arr_len) {
        int * cur_handler = handler;
        for (int i=0; i<arr_len-1 && cur_handler; i++) {
            cur_handler = o1(cur_handler, idx_arr[i]);
        }
        return l1(cur_handler, idx_arr[arr_len-1]);
    }

    /**
     * 通过类似`student.name.parents[0].age`这样的更容易理解的表达式来获取索引数组
     * 索引数组可以用于如上的`v_`, `l_`, `o_`系列函数来获取值
     *
     * @param[in] schema: 描述json结构的schema指针, 该指针通过`xproto`得到
     * @param[in] key_str_info: 表达式字符串
     * @param[out] idx_arr: 返回的索引数组
     * @param[out] arr_len: 返回的索引数组长度
     * @param[out] field: 返回的该表达式对应的field结构体指针
     *
     * @return 0成功; 其他失败
     */
    int get_idxs_by_str(schema_t * schema, xcharpos * key_str_info,
            int * idx_arr, int * arr_len, field_t ** field);

    // 同上, 只是输入表达式参数从xcharpos变成了const char *
    inline int get_idxs_by_str(schema_t * schema, const char * str, int * idx_arr,
            int * arr_len, field_t ** field) {
        xcharpos char_info;
        char_info.str = str;
        char_info.len = strlen(str);
        return get_idxs_by_str(schema, &char_info, idx_arr, arr_len, field);
    }

    /**
     * 该函数将输入的字符串转换为field_t对象数组
     * 用于后续根据该字符串定义获得proto当中的值, 核心是要处理嵌套数组的场景
     *
     * 输入示例：author.friends[].hot_articles[].title
     *
     * @param[in] schema: 某个xproto的描述schema信息
     * @param[in] str: 要解析的用户输入的str
     * @param[out] fields_len/fields: 最后返回的fields信息和长度
     *
     * @return 0成功; 其他代表失败
     */
    int get_fields_by_str(schema_t * schema, const char * str,
            field_t ** fields, int * fields_len);
    /**
     * 配合get_fields_by_str函数，将对象中指定路径的所有值都给返回出来
     *
     * @param[in]: 对象句柄
     * @param[in] fields_len/fields: 要获取值的fields指针长度和地址
     * @param[out] result: 将指定的值转换为string并返回
     *
     * @return 0成功；其他代表失败；
     */
    int get_strs_by_fields(int * handler, field_t ** fields, int fields_len,
            deque<string> & result);

    /**
     * 将序列化后的二进制反解为json
     *
     * @param[in] handler: 二进制句柄指针
     * @param[in] schema:  该二进制包对应的schema指针
     * @param[out] result: 反解出来的json结果
     *
     * @return 0成功; 其他失败
     */
    int get_object_json_str(int * handler, schema_t * schema, string &result);

    /**
     * 将指定的某个字段的值转化为json, 该字段可以是普通字段, object字段, 以及数组字段
     * 甚至是更复杂的嵌套的object[]字段
     * 该函数的几个输入字段都可以通过 get_idxs_by_str 获取
     *
     * @param[in] handler: 二进制句柄
     * @param[in] idx_arr: 要转json的字段索引描述数组
     * @param[in] arr_len: 要转json的字段索引描述数组长度
     * @param[in] field: 要转json的字段信息对象
     * @param[out] result: 解析完毕之后的结果
     *
     * @return 0成功; 其他失败
     */
    int get_field_json_str(int * handler, int * idx_arr, int arr_len,
            field_t * field, string &result);

private:
    // 将一个普通字段(非object/array)字段转化为json字符串
    int _get_simple_type_str(char * value_ptr, int type,
            string &result, bool json_format=true);
};
#endif
